<?php
/**
 * I-Cafe framework
 * 
 * @category CF
 * @package CF_Db
 * @link http://www.i-cafe.org
 * @license http://www.i-cafe.org/license
 * @version 04-04-2010
 * @author tcl_java@yahoo.com
 */

/**
 * A singleton class handler query database
 * <code>
 * 	$dbConfig = array(
 * 		'host'		=> 'localhost',
 * 		'port'		=> 3306,
 * 		'username'	=> 'root',
 * 		'password'	=> 123
 * 	);
 * 	$connection = new CF_Db_Connection($dbConfig);
 * 	$connection->selectDb('mydb');
 * 
 * 	$db = CF_Db::getInstance();
 * 	$db->setConnection($connection);
 * 	$db->query("SELECT * FROM my_table");
 * </code>
 */
class CF_Db extends CF_Component
{
	/**
	 * @var CF_Db Singleton instance
	 */
	protected static $_instance;
	
	/**
	 * Enforce singleton; disallow constructor
	 * @return void
	 */
	protected function __construct(){}
	
	/**
	 * Enforce singleton; disallow cloning
	 * @return void
	 */
	protected function __clone(){}
	
	/**
	 * Retrieve singleton instance
	 * @return CF_Db
	 */
	public static function getInstance()
	{
		if(!self::$_instance){
			self::$_instance = new self();
			
			# register events
			self::$_instance->addEvents(array(
				'beforequery',
				'query'
			));
		}
		
		return self::$_instance;
	}
	
	/**
	 * @var string
	 */
	public $quoteIdentifierSymbol = '`';
	
	/**
	 * An array of CF_Db_Connection
	 * @var array
	 */
	protected $_connections = array();
	
	/**
	 * 
	 * @var CF_Db_Connection
	 */
	protected $_activeConnection;
	
	/**
	 * @var boolean
	 */
	public $enableTransaction = true;
	
	/**
	 * @var boolean
	 */
	public $traceQueryError = false;
	
	public $tablePrefix = 'cf_';
	public $tablePrefixAlias = '##_';
	
	/**
	 * 
	 * @param CF_Db_Connection|string $connection
	 * @return boolean
	 */
	public function hasConnection($con)
	{
		$id = (string) $con;
		return isset($this->_connections[$id]);
	}
	
	/**
	 * Get connection
	 * @param CF_Db_Connection|string
	 * @return CF_Db_Connection
	 */
	public function getConnection($con)
	{
		return $this->hasConnection($con)? $this->_connections[$con] : null;
	}
	
	/**
	 * Set connection
	 * @param CF_Db_Connection $con
	 * @return void
	 */
	public function addConnection(CF_Db_Connection $con)
	{
		$this->_connections[(string) $con] = $con;
	}
	
	/**
	 * 
	 * @param CF_Db_Connection $con
	 * @return CF_Db $this
	 */
	public function setActiveConnection($con)
	{
		$id = (string) $con;
		if (is_string($con) && !$this->hasConnection($id)){
			throw new CF_Db_Exception(
				"Connection \"$id\" does not exists"
			);
		}elseif ($con instanceof CF_Db_Connection){
			$this->addConnection($con);
		}
		
		$this->_activeConnection = $this->_connections[$id];
		return $this;
	}
	
	/**
	 * If no active connection. use first connection
	 * @return CF_Db_Connection
	 */
	public function getActiveConnection()
	{
		if(null == $this->_activeConnection){
			if(empty($this->_connections)){
				throw new CF_Db_Exception(
					"Connection is empty. Please add a connection!"
				);
			}
			reset($this->_connections);
			$this->_activeConnection = current($this->_connections);
		}
		return $this->_activeConnection;
	}
	
	/**
	 * @return array
	 */
	public function getAllConnections()
	{
		return $this->_connections;
	}
	
	/**
	 * Execute a/an query
	 * @param string|array	$queries
	 * @param boolean		$returnReader
	 * @return mixed
	 */
	public function query($queries, $returnStore = true)
	{
		# notify before query event
		if(CF_Observable::VALUE_RETURN === $this->fireEvent('beforequery', array($this, &$queries, &$returnStore))) return;
		
		if($queries instanceof CF_Db_Query_Interface)
			$queries = (string) $queries;
			
		# execute single query
		if(is_string($queries)){
			# for table prefix
			$queries = str_replace($this->tablePrefixAlias, $this->tablePrefix, $queries);
			
			# execute query
			$con = $this->getActiveConnection()->getConnection();
			$result = @mysql_query($queries, $con);

			if(false === $result){
				$msg = $this->getActiveConnection()->getError();
				if($this->traceQueryError) $msg .= "\nSQL: $queries";
				throw new CF_Db_Exception($msg);
			}
			
			# return reader ?
			if($returnStore && is_resource($result)){
				$res = new CF_Db_Store($result);
			}else{
				$res = $result;
			}
		}else{
			if(!$this->enableTransaction){
				# execute each queries
				foreach($queries as $query){
					$this->query($query, false);
				}
				$res = true;
			}else{
				# start transaction
				CF_Db_Transaction::start();
				
				# execute each queries
				foreach($queries as $query){
					try{
						$this->query($query, false);
					}catch(CF_Db_Exception $e){
						# rollback transaction if has a query error
						CF_Db_Transaction::rollback();
						throw new CF_Db_Exception($e->getMessage());
					}
				}
				
				# commit transaction if no error on each queries
				CF_Db_Transaction::commit();
				
				$res = true;
			}
		}
		
		# notify query event
		$this->fireEvent('query', array($this, &$res, &$queries));
		
		return $res;
	}
	
	/**
	 * Get last insert id
	 * @return int
	 */
	public function getInsertId()
	{
		$con = $this->getActiveConnection()->getConnection();
		return @mysql_insert_id($con);
	}
	
	/**
	 * Get affected rows
	 * @return int
	 */
	public function getAfftectedRows()
	{
		$con = $this->getActiveConnection()->getConnection();
		return @mysql_affected_rows($con);
	}
	
	/**
	 * Quote identifier
	 * @param string|array $value
	 * @return string
	 */
	public function quoteIdentifier($data)
	{
		if($data instanceof CF_Db_Expr)
			return (string) $data;
			
		if(is_string($data))
			$data = explode('.', $data);
		
		if(!is_array($data)) return $data;	
		
		$symbol = $this->quoteIdentifierSymbol;
		
		return $symbol . implode($symbol . '.' . $symbol, $data) . $symbol;
	}
	
	/**
	 * Escape string
	 * @param string|array $data
	 * @return mixed
	 */
	public function escape($data)
	{
		if($data instanceof CF_Db_Expr)
			return (string) $data;
			
		if(is_string($data)){
			$con = $this->getActiveConnection()->getConnection();
			return @mysql_real_escape_string($data, $con);
		}
			
		foreach($data as &$v){
			$v = $this->escape($v);
		}
		
		reset($data);
		return $data;
	}
	
	/**
	 * Quote string
	 * @param string|array $data
	 * @param boolean $escape [optional]
	 * @return mixed
	 */
	public function quote($data, $escape = true)
	{
		if(is_array($data)){
			foreach($data as &$v){
				$v = $this->quote($v, $escape);
			}
			reset($data);
			return $data;
		}
		
		if($data instanceof CF_Db_Expr) $data = (string) $data;
		elseif(is_null($data)) $data = "NULL";
		elseif(is_string($data)) $data = "'" . ($escape? $this->escape($data) : $data) . "'";
		return $data;
	}
}
